﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;

namespace StephenEssential
{
    public class Bitmap<TPoint> : IEnumerable<TPoint>
    {
        public readonly int Height;
        public readonly int Width;
        protected internal readonly TPoint[,] Pixels;

        protected Bitmap(int width, int height, TPoint[,] pixels)
        {
            Height = height;
            Width = width;
            Pixels = pixels;
        }
        public Bitmap(Point size, TPoint[,] pixels) : this(size.X, size.Y, pixels) { } 
        public Bitmap(int width, int height) : this(width, height, new TPoint[height,width]) { }
        public Bitmap(Point size) : this(size.X, size.Y) { } 
        public Bitmap(Point size, Func<int, int, TPoint> pixelFunc) : this(size.X, size.Y, pixelFunc) { }
        public Bitmap(Point size, Func<Point, TPoint> pixelFunc) : this(size, (x, y) => pixelFunc(new Point(x, y))) { } 
        public Bitmap(int width, int height, Func<int, int, TPoint> pixleFunc) : this(width, height)
        {
            for (var i = 0; i < height; i++)
                for (var j = 0; j < width; j++)
                    Pixels[i, j] = pixleFunc(j, i);
        }
        public TPoint this[int x, int y]
        {
            get { return Pixels[y, x]; }
            set { Pixels[y, x] = value; }
        }
        public TPoint this[Point p, TPoint defaultValue]
        {
            get { return InRange(p) ? Pixels[p.Y, p.X] : defaultValue; }
        }
        public TPoint this[Point p]
        {
            get { return Pixels[p.Y, p.X]; }
            set { Pixels[p.Y, p.X] = value; }
        }
        public Point Size
        {
            get { return new Point(Width, Height); }
        }
        public int Area
        {
            get { return Width*Height; }
        }
        public Bitmap<TPointO> Select<TPointO>(Func<TPoint, int, int, TPointO> pixelFunc)
        {
            var retMap = new Bitmap<TPointO>(Width, Height);
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    retMap[j, i] = pixelFunc(Pixels[i, j], j, i);
            return retMap;
        }
        

        public Bitmap<TPointO> Select<TPointO>(Func<TPoint, TPointO> pixelFunc)
        {
            return new Bitmap<TPointO>(Size).Pipe(map =>
                {
                    for (var i = 0; i < Height; i++)
                        for (var j = 0; j < Width; j++)
                            map[j, i] = pixelFunc(Pixels[i, j]);
                    return map;
                });
        }
        
        public Bitmap<TPoint> SubMap(Point startPoint, Point blockSize)
        {
            Point tmp;
            return SubMap(startPoint, blockSize, out tmp);
        }

        public Bitmap<TPoint> SubMap(Point startPoint, Point blockSize, out Point qidianRelIslandCoord)
        {
            if (!(startPoint < Size && (startPoint + blockSize) > new Point(0, 0))) throw new ArgumentOutOfRangeException();
            var xParas = Tools.IntervalIntersection.CalcIntersection(startPoint.X, blockSize.X, Width);
            var yParas = Tools.IntervalIntersection.CalcIntersection(startPoint.Y, blockSize.Y, Height);
            var canvas = new Bitmap<TPoint>(xParas.BlockWidth, yParas.BlockWidth);
            for (var i = 0; i < yParas.BlockWidth; i++)
                for (var j = 0; j < xParas.BlockWidth; j++)
                    canvas[j, i] = Pixels[yParas.StartPointRelIntervalB + i, xParas.StartPointRelIntervalB + j];
            qidianRelIslandCoord = new Point(xParas.StartPointRelIntervalA, yParas.StartPointRelIntervalA);
            return canvas;
        }

        protected Bitmap<TPoint> SubMap(Point startPoint, Point blockSize, TPoint defaultValue)
        {
            return new Bitmap<TPoint>(blockSize, (x, y) => this[startPoint + new Point(x, y), defaultValue]);
        }
        public void ForEach(Action<TPoint> action)
        {
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    action(Pixels[i, j]);
        }
        public void ForEach(Action<TPoint, int, int> action)
        {
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    action(Pixels[i, j], j, i);
        }

        public List<TPoint> ToList()
        {
            var retList = new List<TPoint>(Width*Height);
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    retList.Add(Pixels[i,j]);
            return retList;
        }

        public bool InRange(Point point)
        {
            return point >= new Point(0, 0) && point < Size;
        }
        public TPoint[,] GetPixels(bool isDirect = false)
        {
            if (isDirect) return Pixels;
            var retMat = new TPoint[Height,Width];
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    retMat[i, j] = Pixels[i, j];
            return retMat;
        }
        public TPoint[] GetRow(int index)
        {
            if (index < 0 || index >= Height) throw new ArgumentOutOfRangeException("index");
            return new TPoint[Width].Pipe(arr => Width.Loop(i => arr[i] = Pixels[index, i]));
        }
        public List<TPoint[]> GetRows()
        {
            return
                new List<TPoint[]>(Height).Pipe(
                    rows =>
                    Height.Loop(i => rows.Add(new TPoint[Width].Pipe(row => Width.Loop(j => row[j] = Pixels[i, j])))));
        }
        public TPoint[] GetColumn(int index)
        {
            if (index < 0 || index >= Width) throw new ArgumentException();
            return new TPoint[Height].Pipe(arr => Height.Loop(i => arr[i] = Pixels[i, index]));
        }

        public IEnumerator<TPoint> GetEnumerator()
        {
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    yield return Pixels[i, j];
        }

        public override string ToString()
        {
            return Size.ToString() + " " +typeof (TPoint);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public bool All(Func<TPoint, bool> predicate)
        {
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    if (!predicate(Pixels[i, j])) return false;
            return true;
        }
        public bool Any(Func<TPoint, bool> predicate)
        {
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    if (predicate(Pixels[i, j])) return true;
            return false;
        }
        public bool Any(Func<TPoint, int, int, bool> predicate)
        {
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    if (predicate(Pixels[i, j], j, i)) return true;
            return false;
        }
        public int Sum(Func<TPoint, int> sumFunc)
        {
            return Sum((i, x, y) => sumFunc(i));
        }
        public double Sum(Func<TPoint, double> sumFunc)
        {
            return Sum((i, x, y) => sumFunc(i));
        }

        
        public double Sum(Func<TPoint, int, int, double> sumFunc)
        {
            var sum = 0.0;
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    sum += sumFunc(Pixels[i, j], j, i);
            return sum;
        }
        public int Sum(Func<TPoint, int, int, int> sumFunc)
        {
            var sum = 0;
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    sum += sumFunc(Pixels[i, j], j, i);
            return sum;
        }
        public void Print()
        {
            for (var i = 0; i < Height; i++)
            {
                for (var j = 0; j < Width; j++)
                    Console.Write(@"{0}   ", Pixels[i, j]);
                Console.Write("\n");
            }
            Console.ReadLine();
        }

        public int Count(Func<TPoint, bool> predicate)
        {
            var count = 0;
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    if (predicate(this[j, i]))
                        count += 1;
            return count;
        }

        public int Count(Func<TPoint, int, int, bool> predicate)
        {
            var count = 0;
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    if (predicate(this[j, i], j ,i))
                        count += 1;
            return count;
        }

        public List<TPoint> Where(Func<TPoint, int, int, bool> predicate)
        {
            var result = new List<TPoint>();
            ForEach((i, x, y) =>
            {
                if (predicate(i, x, y)) result.Add(i);
            });
            return result;
        }

        public IEnumerable<TOut> SelectWhile<TOut>(Func<TPoint, int, int, TOut> pixelFunc, Func<TPoint, bool> predicate)
        {
            for (var i = 0; i < Height; i++)
                for (var j = 0; j < Width; j++)
                    if (predicate(this[j, i])) yield return pixelFunc(this[j, i], j, i);
        }
    }
}
